package controllers

import (
	"encoding/json"
	"fmt"
	"github.com/gorilla/mux"
	"io/ioutil"
	"net/http"
	"os"
	"sigs.k8s.io/yaml"
	"strings"

	"github.com/golang/glog"
	"github.com/instrumenta/kubeval/kubeval"

	authapi "eccgateway/pkg/api/auth"
	"eccgateway/pkg/controllers/parse"
	"eccgateway/pkg/services/appTempl"
	authsvc "eccgateway/pkg/services/auth"
	"eccgateway/pkg/util"
)

type AppTemplController struct {
	appTempl appTempl.Templ
}

var appTemplController AppTemplController

func GetAppTemplController() AppTemplController {
	appTemplController = AppTemplController{appTempl.NewTempl()}
	return appTemplController
}

func (t AppTemplController) TemplCreate(w http.ResponseWriter, req *http.Request) {
	defer req.Body.Close()
	glog.Infof("start create app template")
	templ := t.appTempl
	info, err := util.GetUserInfo(req.Header.Get(authapi.ParseInfo))
	if err != nil {
		glog.Errorf("get user info from header failed, err: %v", err)
		util.ReturnErrorResponseInResponseWriter(w, http.StatusBadRequest, err.Error())
		return
	}
	requestBody, err := ioutil.ReadAll(req.Body)
	if err != nil {
		glog.Errorf("request body read failed, %v", err)
		util.ReturnErrorResponseInResponseWriter(w, http.StatusBadRequest, fmt.Sprintf("create appTempl request body read failed, %v", err))
		return
	}
	err = json.Unmarshal(requestBody, &templ)
	if err != nil {
		glog.Errorf("unmarshal request body failed, %v", err)
		util.ReturnErrorResponseInResponseWriter(w, http.StatusBadRequest, fmt.Sprintf("unmarshal request body failed, %v", err))
		return
	}
	if json.Valid([]byte(templ.Template)) {
		bytes, err := yaml.JSONToYAML([]byte(templ.Template))
		if err != nil {
			glog.Errorf("json template failed to transform to yaml, %v", err)
			util.ReturnErrorResponseInResponseWriter(w, http.StatusBadRequest, fmt.Sprintf("template failed to transform to yaml, %v", err))
			return
		}
		templ.Template = string(bytes)
	}
	err = validateYaml(templ.Template)
	if err != nil {
		glog.Errorf("deployment yaml is not valid, %v", err)
		util.ReturnErrorResponseInResponseWriter(w, http.StatusBadRequest, fmt.Sprintf("invalid deployment template, %v", err))
		return
	}
	switch req.Method {
	case http.MethodPost:
		res, err := t.appTempl.Create(*info, templ)
		if err != nil {
			glog.Errorf("create appTempl failed, %v", err)
			util.ReturnErrorResponseInResponseWriter(w, http.StatusInternalServerError, fmt.Sprintf("create appTempl failed, %v", err))
			return
		}
		respBody, _ := json.Marshal(res)
		w.WriteHeader(http.StatusCreated)
		_, _ = w.Write(respBody)
	case http.MethodPut:
		err := t.appTempl.UpdateTempl(*info, templ)
		if err != nil {
			glog.Errorf("update appTempl failed, %v", err)
			util.ReturnErrorResponseInResponseWriter(w, http.StatusInternalServerError, fmt.Sprintf("update appTempl failed, %v", err))
			return
		}
		w.WriteHeader(http.StatusOK)
		_, _ = w.Write([]byte("update template success"))
	}
}

func (t AppTemplController) TemplDelete(w http.ResponseWriter, req *http.Request) {
	info, err := util.GetUserInfo(req.Header.Get(authapi.ParseInfo))
	if err != nil {
		glog.Errorf("get user info from header failed, err: %v", err)
		util.ReturnErrorResponseInResponseWriter(w, http.StatusBadRequest, err.Error())
		return
	}
	uuid := mux.Vars(req)["uuid"]
	err = t.appTempl.DeleteTempl(*info, uuid)
	if err != nil {
		glog.Errorf("delete template failed, %v", err)
		util.ReturnErrorResponseInResponseWriter(w, http.StatusInternalServerError, fmt.Sprintf("delete template failed, %v", err))
		return
	}
	w.WriteHeader(http.StatusOK)
	_, _ = w.Write([]byte("success"))
}

func (t AppTemplController) GetTempl(w http.ResponseWriter, req *http.Request) {
	info, err := util.GetUserInfo(req.Header.Get(authapi.ParseInfo))
	if err != nil {
		glog.Errorf("get user info from header failed, err: %v", err)
		util.ReturnErrorResponseInResponseWriter(w, http.StatusBadRequest, err.Error())
		return
	}
	uuid := mux.Vars(req)["uuid"]
	templ, err := t.appTempl.GetTempl(*info, uuid)
	if err != nil {
		glog.Errorf("template not found, %v", err)
		util.ReturnErrorResponseInResponseWriter(w, http.StatusInternalServerError, fmt.Sprintf("template not found, %v", err))
		return
	}
	resp, _ := json.Marshal(templ)
	w.WriteHeader(http.StatusOK)
	_, _ = w.Write(resp)
}

func (t AppTemplController) PubTemplList(w http.ResponseWriter, req *http.Request) {
	dataSelect := parse.ParseDataSelectPathParameter(req)
	templs, count, err := t.appTempl.GetPubTempls(dataSelect)
	if err != nil {
		glog.Errorf("List public template error, %v", err)
		util.ReturnErrorResponseInResponseWriter(w, http.StatusInternalServerError, fmt.Sprintf("no public template found, %v", err))
		return
	}
	respBody, _ := json.Marshal(appTempl.List{
		Count: count,
		Items: templs,
	})
	w.WriteHeader(http.StatusOK)
	_, _ = w.Write(respBody)
}

func (t AppTemplController) TempList(w http.ResponseWriter, req *http.Request) {
	info, err := util.GetUserInfo(req.Header.Get(authapi.ParseInfo))
	if err != nil {
		glog.Errorf("get user info from header failed, err: %v", err)
		util.ReturnErrorResponseInResponseWriter(w, http.StatusBadRequest, err.Error())
		return
	}
	dataSelect := parse.ParseDataSelectPathParameter(req)

	templs, count, err := t.appTempl.ListTemplByUserId(authsvc.GetManageUserIDs(info), dataSelect)
	if err != nil {
		glog.Errorf("List appTempl failed, %v", err)
		util.ReturnErrorResponseInResponseWriter(w, http.StatusInternalServerError, fmt.Sprintf("List appTempl failed, %v", err))
		return
	}

	respBody, _ := json.Marshal(appTempl.List{
		Count: count,
		Items: templs,
	})
	w.WriteHeader(http.StatusOK)
	_, _ = w.Write(respBody)
}

func (t AppTemplController) PublicHandler(w http.ResponseWriter, req *http.Request) {
	info, err := util.GetUserInfo(req.Header.Get(authapi.ParseInfo))
	if err != nil {
		glog.Errorf("get user info from header failed, err: %v", err)
		util.ReturnErrorResponseInResponseWriter(w, http.StatusBadRequest, err.Error())
		return
	}
	templUuid := mux.Vars(req)["uuid"]
	glog.Infof("Get request to set template %s public.", templUuid)
	_, err = t.appTempl.SetTemplPublic(*info, templUuid)
	if err != nil {
		glog.Errorf("set template public failed, %v", err)
		util.ReturnErrorResponseInResponseWriter(w, http.StatusInternalServerError, fmt.Sprintf("set template public failed, %v", err))
		return
	}
	w.WriteHeader(http.StatusOK)
	_, _ = w.Write([]byte("Request submitted"))
}

func (t AppTemplController) PrivateHandler(w http.ResponseWriter, req *http.Request) {
	info, err := util.GetUserInfo(req.Header.Get(authapi.ParseInfo))
	if err != nil {
		glog.Errorf("get user info from header failed, err: %v", err)
		util.ReturnErrorResponseInResponseWriter(w, http.StatusBadRequest, err.Error())
		return
	}
	templUuid := mux.Vars(req)["uuid"]
	glog.Infof("set template (%s) private by %s", templUuid, info.Name)
	templ, err := t.appTempl.SetTemplPrivate(*info, templUuid)
	if err != nil {
		glog.Errorf("set template private failed, %v", err)
		util.ReturnErrorResponseInResponseWriter(w, http.StatusInternalServerError, fmt.Sprintf("set template private failed, %v", err))
		return
	}
	resp, _ := json.Marshal(struct {
		Name     string
		Creator  string
		Modifier string
		Privacy  string
		Status   int
		Version  string
		StarCount int
		DownloadCount int
	}{templ.Name,
		templ.Creator,
		templ.Modifier,
		templ.Privacy,
		templ.Status,
		templ.Version,
		templ.StarCount,
		templ.DownloadCount,
	})
	w.WriteHeader(http.StatusOK)
	_, _ = w.Write(resp)
}

func (t AppTemplController) AuditHandler(w http.ResponseWriter, req *http.Request) {
	defer req.Body.Close()
	glog.Infof("audit progress start")
	templ := t.appTempl
	useInfo, err := util.GetUserInfo(req.Header.Get(authapi.ParseInfo))
	if err != nil {
		glog.Errorf("get user info from header failed, err: %v", err)
		util.ReturnErrorResponseInResponseWriter(w, http.StatusBadRequest, err.Error())
		return
	}
	if appTempl.GetHighestAuthority(*useInfo) != 1 {
		glog.Errorf("operation is not permitted")
		util.ReturnErrorResponseInResponseWriter(w, http.StatusBadRequest, "operation is not permitted")
		return
	}

	requestBody, err := ioutil.ReadAll(req.Body)
	if err != nil {
		glog.Errorf("request body read failed, %v", err)
		util.ReturnErrorResponseInResponseWriter(w, http.StatusBadRequest, fmt.Sprintf("request body read failed, %v", err))
		return
	}
	err = json.Unmarshal(requestBody, &templ)
	if err != nil {
		glog.Errorf("unmarshal request body failed, %v", err)
		util.ReturnErrorResponseInResponseWriter(w, http.StatusBadRequest, fmt.Sprintf("unmarshal request body failed, %v", err))
		return
	}
	var respBody []byte
	if templ.AuditResult == "1" {
		respBody = []byte(templ.Desc)
		_, err := t.appTempl.AuditApprove(*useInfo, templ)
		if err != nil {
			glog.Errorf("update template failed, %v", err)
			util.ReturnErrorResponseInResponseWriter(w, http.StatusInternalServerError, fmt.Sprintf("update template failed, %v", err))
			return
		}
	} else {
		te, err := t.appTempl.AuditApprove(*useInfo, templ)
		if err == appTempl.TemplstatusError {
			glog.Errorf("template is not set public, %v", err)
			util.ReturnErrorResponseInResponseWriter(w, http.StatusBadRequest, fmt.Sprintf("please set the template public first, %v", err))
			return
		}
		if err == appTempl.TemplAlreadyPublic {
			glog.Error("template is already public")
			util.ReturnErrorResponseInResponseWriter(w, http.StatusForbidden, fmt.Sprint("template is already public"))
			return
		}
		if err != nil {
			glog.Errorf("approve template public failed, %v", err)
			util.ReturnErrorResponseInResponseWriter(w, http.StatusInternalServerError, fmt.Sprintf("approve template public failed, %v", err))
			return
		}
		respBody, _ = json.Marshal(appTempl.Response{
			Name:    te.Name,
			Auditor: te.Auditor,
			Version: te.Version,
			Log:     "operation success " + te.Log,
		})
	}
	w.WriteHeader(http.StatusOK)
	_, _ = w.Write(respBody)
}

func (t AppTemplController) GetTemplLogs(w http.ResponseWriter, req *http.Request) {
	_, err := util.GetUserInfo(req.Header.Get(authapi.ParseInfo))
	if err != nil {
		glog.Errorf("get user info from header failed, err: %v", err)
		util.ReturnErrorResponseInResponseWriter(w, http.StatusBadRequest, err.Error())
		return
	}
	vars := mux.Vars(req)
	templName := vars["templName"]
	version := vars["version"]
	logs, err := t.appTempl.GetLogs(templName, version)
	if err != nil {
		glog.Errorf("get template logs %s failed, %v", templName, err)
		util.ReturnErrorResponseInResponseWriter(w, http.StatusInternalServerError, fmt.Sprintf("get logs failed, %v", err))
	}
	resp, _ := json.Marshal(logs)
	w.WriteHeader(http.StatusOK)
	_, _ = w.Write(resp)
}

func validateYaml(yamlContent string) error {
	dir, _ := os.Getwd()
	SchemaLocation := "file:///" + strings.Replace(dir, "\\", "/", -1) + "/public/"
	config := kubeval.Config{
		DefaultNamespace:          "default",
		KubernetesVersion:         "",
		SchemaLocation:            SchemaLocation,
		AdditionalSchemaLocations: nil,
		OpenShift:                 false,
		Strict:                    true,
		IgnoreMissingSchemas:      false,
		ExitOnError:               false,
		KindsToSkip:               nil,
		KindsToReject:             nil,
		FileName:                  "",
		OutputFormat:              "",
		Quiet:                     false,
		InsecureSkipTLSVerify:     false,
	}
	bytes := []byte(yamlContent)
	results, e := kubeval.Validate(bytes, &config)
	if e != nil {
		return e
	}
	for _, result := range results {
		if len(result.Errors) != 0 {
			return fmt.Errorf("%s is not valid for: %v", result.Errors)
		}
	}
	return nil
}

func (t AppTemplController) DownLoadHandler(w http.ResponseWriter, req *http.Request) {
	info, err := util.GetUserInfo(req.Header.Get(authapi.ParseInfo))
	if err != nil {
		glog.Errorf("get user info from header failed, err: %v", err)
		util.ReturnErrorResponseInResponseWriter(w, http.StatusBadRequest, err.Error())
		return
	}
	templUuid := mux.Vars(req)["uuid"]
	glog.Infof("Get request to download template %s .", templUuid)
	_, err = t.appTempl.UpdateTemplDownLoad(*info, templUuid)
	if err != nil {
		glog.Errorf("set template download failed, %v", err)
		util.ReturnErrorResponseInResponseWriter(w, http.StatusInternalServerError, fmt.Sprintf("set template download failed, %v", err))
		return
	}
	w.WriteHeader(http.StatusOK)
	_, _ = w.Write([]byte("Request submitted"))
}
func (t AppTemplController) IncreaseStarHandler(w http.ResponseWriter, req *http.Request) {
	info, err := util.GetUserInfo(req.Header.Get(authapi.ParseInfo))
	if err != nil {
		glog.Errorf("get user info from header failed, err: %v", err)
		util.ReturnErrorResponseInResponseWriter(w, http.StatusBadRequest, err.Error())
		return
	}
	templUuid := mux.Vars(req)["uuid"]
	glog.Infof("Get request to increase star template %s by  userid is %s and userName is %s .", templUuid, info.UserID, info.Name)
	_, err = t.appTempl.IncreaseTemplStar(*info, templUuid)
	if err != nil {
		glog.Errorf("set template star failed, %v", err)
		util.ReturnErrorResponseInResponseWriter(w, http.StatusInternalServerError, fmt.Sprintf("set template star failed, %v", err))
		return
	}
	w.WriteHeader(http.StatusOK)
	_, _ = w.Write([]byte("Request submitted"))
}
func (t AppTemplController) DecreaseStarHandler(w http.ResponseWriter, req *http.Request) {
	info, err := util.GetUserInfo(req.Header.Get(authapi.ParseInfo))
	if err != nil {
		glog.Errorf("get user info from header failed, err: %v", err)
		util.ReturnErrorResponseInResponseWriter(w, http.StatusBadRequest, err.Error())
		return
	}
	templUuid := mux.Vars(req)["uuid"]
	glog.Infof("Get request to decrease template for star %s .", templUuid)
	err = t.appTempl.DecreaseStarTempl(*info, templUuid)
	if err != nil {
		glog.Errorf(" decrease template for star  failed, %v", err)
		util.ReturnErrorResponseInResponseWriter(w, http.StatusInternalServerError, fmt.Sprintf("decrease template for star failed, %v", err))
		return
	}
	w.WriteHeader(http.StatusOK)
	_, _ = w.Write([]byte("Request submitted"))
}

func (t AppTemplController) GetStarTempls(w http.ResponseWriter, req *http.Request) {
	info, err := util.GetUserInfo(req.Header.Get(authapi.ParseInfo))
	if err != nil {
		glog.Errorf("get user info from header failed, err: %v", err)
		util.ReturnErrorResponseInResponseWriter(w, http.StatusBadRequest, err.Error())
		return
	}
	dataSelect := parse.ParseDataSelectPathParameter(req)

	templs, count, err := t.appTempl.ListStarTemplByUserId(authsvc.GetManageUserIDs(info), dataSelect)
	if err != nil {
		glog.Errorf("List appTempl failed, %v", err)
		util.ReturnErrorResponseInResponseWriter(w, http.StatusInternalServerError, fmt.Sprintf("List appTempl failed, %v", err))
		return
	}

	respBody, _ := json.Marshal(appTempl.List{
		Count: count,
		Items: templs,
	})
	w.WriteHeader(http.StatusOK)
	_, _ = w.Write(respBody)

}
